package com.rrd.hive.udf.brickhouse.udf.timeseries;

import java.util.Map;

import org.apache.hadoop.hive.ql.exec.Description;
import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDF;
import org.apache.hadoop.hive.serde2.objectinspector.ListObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.MapObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector.Category;
import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.StandardListObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.StandardMapObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
import org.apache.log4j.Logger;

/**
 * Magnitude of vector = SQRT( Sum( val^2 ) )
 */
@Description(name = "vector_magnitude", value = " Magnitude of a vector.")
public class VectorMagnitudeUDF extends GenericUDF {
    private static final Logger LOG = Logger
        .getLogger(VectorMagnitudeUDF.class);
    private ListObjectInspector listInspector;
    private MapObjectInspector mapInspector;
    private PrimitiveObjectInspector valueInspector;

    private StandardListObjectInspector retListInspector;
    private StandardMapObjectInspector retMapInspector;

    public Object evaluateList(Object listObj) {
        double sumSquares = 0.0;
        for (int i = 0; i < this.listInspector.getListLength(listObj); ++i) {
            Object listVal = this.listInspector.getListElement(listObj, i);
            double listDbl = NumericUtil.getNumericValue(this.valueInspector,
                listVal);
            sumSquares += listDbl * listDbl;
        }
        return Math.sqrt(sumSquares);
    }

    public Object evaluateMap(Object uninspMapObj) {
        Map map = this.mapInspector.getMap(uninspMapObj);
        double sumSquares = 0.0;
        for (Object mapKey : map.keySet()) {
            Object mapValObj = map.get(mapKey);
            double mapValDbl = NumericUtil.getNumericValue(this.valueInspector,
                mapValObj);

            sumSquares += mapValDbl * mapValDbl;
        }
        return Math.sqrt(sumSquares);
    }

    @Override
    public Object evaluate(DeferredObject[] arg0) throws HiveException {
        if (this.listInspector != null) {
            return this.evaluateList(arg0[0].get());
        } else {
            return this.evaluateMap(arg0[0].get());
        }
    }

    @Override
    public String getDisplayString(String[] arg0) {
        return "vector_scalar_mult";
    }

    private void usage(String message) throws UDFArgumentException {
        VectorMagnitudeUDF.LOG.error(
            "vector_scalar_mult: Multiply a vector times a scalar value : "
                + message);
        throw new UDFArgumentException(
            "vector_scalar_mult: Multiply a vector times a scalar value : "
                + message);
    }

    @Override
    public ObjectInspector initialize(ObjectInspector[] arg0)
            throws UDFArgumentException {
        if (arg0.length != 2) {
            this.usage("Must have two arguments.");
        }

        if (arg0[0].getCategory() == Category.MAP) {
            this.mapInspector = (MapObjectInspector) arg0[0];

            if (this.mapInspector.getMapKeyObjectInspector()
                .getCategory() != Category.PRIMITIVE) {
                this.usage("Vector map key must be a primitive");
            }

            if (this.mapInspector.getMapValueObjectInspector()
                .getCategory() != Category.PRIMITIVE) {
                this.usage("Vector map value must be a primitive");
            }

            this.valueInspector = (PrimitiveObjectInspector) this.mapInspector
                .getMapValueObjectInspector();
        } else if (arg0[0].getCategory() == Category.LIST) {
            this.listInspector = (ListObjectInspector) arg0[0];

            if (this.listInspector.getListElementObjectInspector()
                .getCategory() != Category.PRIMITIVE) {
                this.usage("Vector array value must be a primitive");
            }

            this.valueInspector = (PrimitiveObjectInspector) this.listInspector
                .getListElementObjectInspector();
        } else {
            this.usage("First argument must be an array or map");
        }

        if (!NumericUtil
            .isNumericCategory(this.valueInspector.getPrimitiveCategory())) {
            this.usage(" Vector values must be numeric");
        }
        if (arg0[1].getCategory() != Category.PRIMITIVE) {
            this.usage(" scalar needs to be a primitive type.");
        }

        return PrimitiveObjectInspectorFactory.javaDoubleObjectInspector;
    }
}
